#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <math.h>

#include "lista.h"
#include "gestor.h"
#include "partido.h"
#include "equipo.h"
#include "jugador.h"
#include "heap.h"
#include "hash.h"
#include "gestor.h"
#include "partido.h"
#include "cola.h"


#define MAX_NOMBRE_PAIS 50
#define MAX_NOMBRE_JUGADOR 100
#define CANTIDAD_JUGADORES 23
#define ASCII_RETORNO_CARRO 13
#define MAX_CODIGO_PARTIDO 5
#define X_DORSAL "dorsal"
#define X_NOMBRE "nombre"


char obtener_proxima_rama(char rama);

struct gestor
{	 
	 hash_t *fixture;
	 heap_t *goleadores;
	 hash_t *equipos;
	 hash_t *jugadores;
	 
	 cola_t *orden_ingreso;
};

int comparar_goles(const void *a,const void *b)
{ 
	if(jugador_obtener_goles(a) > jugador_obtener_goles(b))
		return 1;
	else if(jugador_obtener_goles(a) < jugador_obtener_goles(b))
		return -1;
	else
		return 0;
}

gestor_t *gestor_crear()
{
	gestor_t *gestor = malloc(sizeof(gestor_t));
	if(!gestor)
		return NULL;
	
	// Guardo los equipos en un hash
	gestor->equipos = hash_crear((hash_destruir_dato_t)equipo_destruir);
	if(!gestor->equipos)
	{
		free(gestor);
		return NULL;
	}
	
	// Guardo los jugadores en un hash
	gestor->jugadores = hash_crear(NULL);
	if(!gestor->jugadores)
	{
		hash_destruir(gestor->equipos);
		free(gestor);		
		return NULL;
	}
	
	// Guardo los goleadores en un heap de máxima
	gestor->goleadores = heap_crear((cmp_func_t)comparar_goles);
	if(!gestor->goleadores)
	{
		hash_destruir(gestor->jugadores);
		hash_destruir(gestor->equipos);
		free(gestor);		
		return NULL;		
	}
		
	// Guardo los partidos en un hash
	gestor->fixture = hash_crear((hash_destruir_dato_t)partido_destruir);
	if(!gestor->fixture)
	{
		heap_destruir(gestor->goleadores,NULL);
		hash_destruir(gestor->jugadores);
		hash_destruir(gestor->equipos);
		free(gestor);		
		return NULL;		
	}
	
	// Guardo el orden de ingreso en una cola
	gestor->orden_ingreso = cola_crear();
	if(!gestor->orden_ingreso)
	{
		hash_destruir(gestor->fixture);
		heap_destruir(gestor->goleadores,NULL);
		hash_destruir(gestor->jugadores);
		hash_destruir(gestor->equipos);
		free(gestor);		
		return NULL;		
	}
	
	return gestor;
}

int gestor_cantidad_partidos(int cant_equipos,int *rondas)
{
	int result = 0;
	int divisor = 2;
	*rondas = 0;
	while(cant_equipos/divisor >= 1)
	{
		result += cant_equipos/divisor;
		divisor *= 2;
		(*rondas)++;
	}
	return result;
}

void gestor_crear_primeros_partidos(gestor_t *gestor,lista_t *lista)
{
	if(!gestor && !lista)
		return;
}

bool cargar_equipos(gestor_t *gestor, char *archivo)
{    
	FILE *fp;
	fp = fopen(archivo, "r");
	if(!fp)
	{
		fprintf(stderr,"ERROR: no se puede abrir el archivo %s\n",archivo);
		return false;
	}
	
	char *pais;
	
    do
	{	
		// Pido memoria para el país
		pais = malloc(sizeof(char) * MAX_NOMBRE_PAIS);
		
		if(!pais)
		{			
			fprintf(stderr,"ERROR: pais es NULL\n");
			return false;
		}
		
		// Inicializo pais para evitar un warining
		pais[0] = '\0';	
		
		// Si fgets devuelve NULL
		// estoy al final del archivo
		if(!fgets(pais,MAX_NOMBRE_PAIS,fp))
			break;
		
		if(!strcmp(pais,""))
			break;
	
		// fgets copia el ascii 13 retorno de carro
		// Reemplazo el retorno de carro por '\0'		
		for(unsigned i=0; i<strlen(pais);i++)		
		{
			if(pais[i] == ASCII_RETORNO_CARRO)
			{
				pais[i] = '\0';
				break;
			}			
		}
		
		// Pido memoria para el arreglo de jugadores
		jugador_t **jugadores = malloc(sizeof(jugador_t*)*CANTIDAD_JUGADORES);
		
		if(!jugadores)
		{
			fprintf(stderr,"ERROR: arreglo jugadores es NULL para el equipo %s",pais);
			return false;
		}			

		for(unsigned i=0;i<CANTIDAD_JUGADORES;i++)
		{
			// Pido memoria para el nombre del jugador
			char *nombre_jugador = malloc(sizeof(char)*MAX_NOMBRE_JUGADOR);
			
			nombre_jugador[0] = '\0';

			fgets(nombre_jugador,MAX_NOMBRE_JUGADOR,fp);					
		
			// Controlo el EOF
			if(!nombre_jugador)
			{
				fprintf(stderr,"AVISO: jugadores[%d] es NULL",i);				
				return false;
			}
				
			// Idem
			// Reemplazo el retorno de carro por '\0'
			for(unsigned j=0; j<strlen(nombre_jugador);j++)
			{
				if(nombre_jugador[j] == ASCII_RETORNO_CARRO)
				{
					nombre_jugador[j] = '\0';
					break;
				}			
			}	

			jugador_t *jugador = jugador_crear(nombre_jugador,pais,i+1);
			if(!jugador)
			{
				fprintf(stderr,"ERROR: jugador es NULL\n");
				return false;
			}
			
			// Agrego el jugador al arreglo
			jugadores[i] = jugador;
			
			// Agrego el jugador al hash de jugadores
			if(!hash_guardar(gestor->jugadores,nombre_jugador,jugador))			
			{
				fprintf(stderr,"ERROR: hash guardar en jugadores devuelve falso\n");
				return false;
			}
			
			// Agrego el jugador al heap de goleadores
			if(!heap_encolar(gestor->goleadores,jugador))
			{
				fprintf(stderr,"ERROR: heap_encolar devuelve falso\n");
				return false;
			}
							
		}		
		
		// Creo un equipo
		equipo_t *equipo = equipo_crear(pais,jugadores);				
		
		if(!equipo)
		{
			fprintf(stderr,"ERROR: equipo_crear devuelve NULL\n");
			return false;
		}		
					
		// Agrego el equipo al hash de equipos
		if(!hash_guardar(gestor->equipos,pais,equipo))
		{
			fprintf(stderr,"ERROR: hash_guardar equipo devuelve falso\n");
			return false;						
		}
		
		// Encolo el equipo
		// para respetar el orden
		if(!cola_encolar(gestor->orden_ingreso,equipo))
		{
			fprintf(stderr,"ERROR: cola_encolar equipo devuelve falso\n");
			return false;			
		}		
		
		// Controlo el EOF       
		if(feof(fp))
			break;
							
	}while(true); 
	
	// Libero el ultimo pedido de memoria
	// ya que no se usa.
	free(pais);  

	fclose(fp);	
	
	return true;
}

bool armar_fixture(gestor_t *gestor)
{
	int cant_equipos = hash_cantidad(gestor->equipos);
	
	int cant_partidos = cant_equipos/2;
	
	char *codigo; 
		
	char letra = 'a';
	
	int instancia_inicial = cant_partidos;
	
	// Genero la fase inicial
	for(int i=0;i<cant_partidos;i++)
	{
		// Reservo memoria para el codigo
		codigo = malloc(sizeof(char) * MAX_CODIGO_PARTIDO);
		if(!codigo)
			return false;

		// A la cantidad de partidos iniciales
		// le agrego la letra
		sprintf(codigo,"%d%c",cant_partidos,letra);
		
		// Desencolo los equipos
		equipo_t *local = cola_desencolar(gestor->orden_ingreso);
		equipo_t *visitante = cola_desencolar(gestor->orden_ingreso);
		
		partido_t *partido = partido_crear(codigo,instancia_inicial,letra, local, visitante);
		if(!partido)
		{
			free(codigo);
			return false;
		}
		
		//partido_mirar(partido);	
		
		if(!hash_guardar(gestor->fixture,codigo,partido))
		{
			fprintf(stderr,"ERROR: hash_guardar partido devuelve falso\n");
			return false;
		}
		
		letra++;
	}
		
	// PRUEBA: que tiene el fixture
	/*
	hash_iter_t *iter = hash_iter_crear(gestor->fixture);
	if(!iter)
		return false;
	
	while(!hash_iter_al_final(iter))
	{
		const char *clave = hash_iter_ver_actual(iter);
		
		partido_t *p = hash_obtener(gestor->fixture,clave);
		
		partido_mirar(p);
		
		hash_iter_avanzar(iter);
	}
	
	hash_iter_destruir(iter);		
	*/
	return true;
}

bool gestor_cargar_datos(gestor_t *gestor,int argc,char *argv[])
{
	// Verifico la cantidad de argumentos
	if(argc != 2)
	{
		fprintf(stderr,"ERROR: la cantidad de argumentos debe ser 2. Ud. ingreso %d\n",argc);
		return false;
	}
	
	// Cargo los equipos y los jugadores
	if(!cargar_equipos(gestor,argv[1]))
	{
		fprintf(stderr,"ERROR: cargar_equipos devuelve falso\n");
		return false;
	}
	
	// Armo el fixture
	if(!armar_fixture(gestor))
	{
		fprintf(stderr,"ERROR: armar_fixture devuelve falso\n");
		return false;
	}
	
	
	// Que hay en el hash	
	/*
	hash_iter_t *iter = hash_iter_crear(gestor->equipos);
	if(!iter)
	{
		fprintf(stderr,"ERROR: iter es NULL");
		return false;
	}
	
	while(!hash_iter_al_final(iter))
	{
		const char *clave = hash_iter_ver_actual(iter);
		
		equipo_t *e = hash_obtener(gestor->equipos,clave);
		
		if(!e)
		{
			fprintf(stderr,"ERROR: e es NULL");
			return false;
		}		
		
		equipo_mirar(e);
				
		hash_iter_avanzar(iter);
	}
	
	hash_iter_destruir(iter);
	*/
	
	return true;
}

void gestor_agregar_resultado(gestor_t *gestor,char *idr, int gloc, int gvis,int *goleadores_por_dorsal)
{
	// Obtengo el partido en O(1)
	partido_t *partido = hash_obtener(gestor->fixture,idr);
	if(!partido)
	{
		printf("Error: el resultado con id %s no existe\n",idr);
		fprintf(stderr,"ERROR: no existe el partido \"%s\"\n",idr);
		free(idr);
		return;
	}
	
	// Obtengo los equipos del partido en O(1)
	equipo_t *local	= partido_obtener_local(partido);
	equipo_t *visitante = partido_obtener_visitante(partido);
	
	// Verifico si el partido esta pendiente
	if(!partido_esta_pendiente(partido))	
	{
		printf("Error: el resultado con id %s ya existe\n",idr);
		fprintf(stderr,"Error: el resultado con id %s ya existe\n",idr);
		free(idr);
		return;
	}
	
	if(!visitante || !local)
	{
		printf("Error: el resultado con id %s no existe\n",idr);
		fprintf(stderr,"ERROR: local=%p visitante=%p\n",(void *)local,(void *)visitante);
		free(idr);
		return;
	}
	
	// Guardo el resultado
	partido_agregar_resultado(partido,gloc,gvis);
	
	// Agrego los goles a los jugadores
	for(int i=0;i < gloc + gvis;i++)
	{	
		equipo_t *equipo_del_goleador;
		
		if(i<gloc)	
			equipo_del_goleador = local;		
		else	
			equipo_del_goleador = visitante;
					
		jugador_t *jugador = equipo_buscar_jugador_por_dorsal(equipo_del_goleador,goleadores_por_dorsal[i]);
		
		jugador_agregar_gol(jugador);		
	}
	
	// Libero la memoria para el código
	free(idr);
	
	// Actualizo el heap de goleadores
	heap_heapify(gestor->goleadores);
	
	// Promover
	// Identificar el equipo ganador
	equipo_t *ganador;
	
	if(gloc > gvis)
		ganador = local;
	else
		ganador = visitante;
		
	// Identificar el código del próximo partido
	unsigned instancia = partido_obtener_instancia(partido)/2;
	char rama = obtener_proxima_rama(partido_obtener_rama(partido));
	
	// Creo el código nuevo
	char *codigo = malloc(sizeof(char) * MAX_CODIGO_PARTIDO);
	if(!codigo)
	{
		fprintf(stderr,"ERROR: codigo es NULL\n");
		return;
	}	
	
	sprintf(codigo,"%d%c",instancia,rama);
	
	//printf("Próximo codigo de partido \"%s\"\n",codigo);
	
	// Verifico si el partido exsite
	partido_t *proximo_partido = hash_obtener(gestor->fixture,codigo);
	
	
	// Si existe, agrego el equipo visitante (?)
	if(proximo_partido)
	{
		partido_agregar_visitante(proximo_partido,ganador);
		
		// Si el partido existe
		// libero la memoria para el codigo
		free(codigo);
		
		printf("OK\n");	
		return;
	}
	
	// Si no existe,
	// lo creo y agrego el equipo como local (?)
	proximo_partido = partido_crear(codigo,instancia,rama,ganador,NULL);
	
	if(!proximo_partido)
	{
		fprintf(stderr,"ERROR: proximo_partido es NULL\n");
		free(codigo);
		return;
	}
	
	// Agrego el partido al fixture
	if(!hash_guardar(gestor->fixture,codigo,proximo_partido))
	{
		fprintf(stderr,"ERROR: guardar en el fixture deuvleve falso\n");		
		return;		
	}
	
	printf("OK\n");			
}

void gestor_listar_jugadores(gestor_t *gestor, char *filtro, char *pais)
{
	// Obtengo el pais en O(1)
	equipo_t *equipo = hash_obtener(gestor->equipos,pais);
	
	if(!equipo)
	{
		printf("Error: el equipo %s no esta inscripto en el fixture\n",pais);
		fprintf(stderr,"ERROR: el pais %s no existe\n",pais);
		free(filtro);
		return;
	}
	

	if(!strcmp(X_DORSAL,filtro))
		equipo_imprimir_por_dorsal(equipo);
	
	if(!strcmp(X_NOMBRE,filtro))
		equipo_imprimir_por_nombre(equipo);
		
	// Libero la memoria para el filtro
	free(filtro);	
	
}

void gestor_listar_goleador(gestor_t *gestor)
{
	jugador_t *jugador = heap_ver_max(gestor->goleadores);
	
	if(!jugador)
	{
		fprintf(stderr,"ERROR: jugador es NULL\n");
		return;
	}
	
	jugador_imprimir(jugador);
}

void gestor_goles_jugador(gestor_t *gestor,char *nombre)
{
	// Obtengo el jugador en O(1)
	jugador_t *jugador = hash_obtener(gestor->jugadores,nombre);
	
	if(!jugador)
	{
		printf("Error: el jugador %s no esta inscripto en el fixture\n",nombre);
		fprintf(stderr,"ERROR: jugador es NULL\n");
		return;		
	}
	
	jugador_imprimir_goles(jugador);
}

void gestor_mostrar_resultado(gestor_t *gestor,char *idr)
{
	// Obtengo el partido en O(1)
	partido_t *partido = hash_obtener(gestor->fixture,idr);
	if(!partido)
	{
		printf("Error : el resultado con id %s no existe\n",idr);
		fprintf(stderr,"ERROR: el partido con idr \"%s\"no existe\n",idr);
		free(idr);
		return;
	}
	
	if(partido_esta_pendiente(partido))
	{
		printf("Error : el resultado con id %s no existe\n",idr);
		free(idr);
		return;
	}
	
	// Libero la memoria
	free(idr);
	
	partido_imprimir(partido);
}

void gestor_destruir(gestor_t *gestor)
{
	cola_destruir(gestor->orden_ingreso,NULL);	
	
	hash_destruir(gestor->equipos);
	
	hash_destruir(gestor->fixture);	
	
	hash_destruir(gestor->jugadores);	
	
	heap_destruir(gestor->goleadores,NULL);
	
	free(gestor);
}

/*********************************************************************
 * 				FUNCIONES INTERNAS
 *********************************************************************/

char obtener_proxima_rama(char rama)
{
	switch(rama)
	{
		case 'a':
		case 'b':
			return 'a';	
		case 'c':
		case 'd':
			return 'b';
		case 'e':
		case 'f':
			return 'c';
		case 'g':
		case 'h': 
			return 'd';
		case 'i':
		case 'j': 
			return 'e';
		case 'k':
		case 'l': 
			return 'f';
		case 'm':
		case 'n': 
			return 'g';
		case 'o':
		case 'p': 
			return 'h';
		default:
			return '\0';
	}
}

